//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//********************************************************* 

using System;
using System.Globalization;
using System.Runtime.InteropServices;
using Microsoft.Office.Core;
using Microsoft.Office.Interop.Word;
using Microsoft.TridentWordAddIn.Common;

namespace Microsoft.TridentWordAddIn.AddIn
{
    /// <summary>
    /// Creates and deletes custom xml parts
    /// </summary>
    public class CustomXmlPartManager : IDocumentStorage
    {
        private const string CatalogNamespace = "Microsoft.TridentWordAddIn.AddIn.CustomXmlPartDocStorage";
        private const string GuidNode = "@guid";
        private const string IdNode = "@id";
        private readonly Application wordApplication;
        private static object missingObject = Type.Missing;

        /// <summary>
        /// Initializes a new instance of the <see cref="CustomXmlPartManager"/> class.
        /// </summary>
        /// <param name="wordApplication">The word application.</param>
        public CustomXmlPartManager(Application wordApplication)
        {
            this.wordApplication = wordApplication;
        }

        /// <summary>
        /// Gets or sets the package host.
        /// </summary>
        /// <value></value>
        /// <param name="packageHost">The package host.</param>
        public IThisAddInPackageHost PackageHost { get; set; }

        #region IDocumentStorage Members

        /// <summary>
        /// Removes the child nodes.
        /// </summary>
        /// <param name="customXmlNodeRoot">The custom XML node root.</param>
        /// <param name="id">The id.</param>
        /// <param name="nodeType">Type of the node.</param>
        public static void RemoveChildNodes(CustomXMLNode customXmlNodeRoot, string id, string nodeType)
        {
            CustomXMLNodes customXMLNodes = customXmlNodeRoot.SelectNodes("//xmlpart");
            CustomXMLNode idNode;

            foreach (CustomXMLNode node in customXMLNodes)
            {
                idNode = node.SelectSingleNode(nodeType);
                if (idNode.NodeValue.CompareIgnoreCase(id))
                {
                    customXmlNodeRoot.RemoveChild(node);
                }
            }
        }

        /// <summary>
        /// Gets the custom XML part by id.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="id">The id.</param>
        /// <returns>CustomXMLPart</returns>
        public CustomXMLPart GetCustomXmlPartById(int documentKey, string id)
        {
            Document wordDocument = this.PackageHost.GetDocumentByDocumentKey(documentKey);
            CustomXMLPart catalog = GetCatalog(wordDocument);
            if (catalog != null)
            {
                CustomXMLNode root = catalog.DocumentElement;
                string storedGuid = GetCustomXMLNodeGuid(id, root);

                if (storedGuid.IsNullOrEmpty())
                {
                    return CreateCustomXMLPart(id, root, wordDocument);
                }

                CustomXMLPart newCustomXmlPart = wordDocument.CustomXMLParts.SelectByID(storedGuid);
                if (newCustomXmlPart == null)
                {
                    foreach (CustomXMLPart part in wordDocument.CustomXMLParts)
                    {
                        if (part.Id.CompareIgnoreCase(storedGuid))
                        {
                            newCustomXmlPart = part;
                            break;
                        }
                    }

                    if (newCustomXmlPart == null)
                    {
                        return CreateCustomXMLPart(id, root, wordDocument);
                    }
                }

                return newCustomXmlPart;
            }

            return null;
        }

        /// <summary>
        /// Gets the XML part with the passed guid, or null if it doesn't exist.
        /// </summary>
        /// <param name="guid">The GUID.</param>
        /// <returns>CustomXMLPart</returns>
        public CustomXMLPart GetCustomXmlPartByGuid(Guid guid)
        {
            string strGuid = "{" + guid.ToString().ToUpper(CultureInfo.InvariantCulture) + "}";
            return this.wordApplication.ActiveDocument.CustomXMLParts.SelectByID(strGuid);
        }

        /// <summary>
        /// Gets the XML part with the passed guid, or null if it doesn't exist.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="guid">The GUID.</param>
        /// <returns>CustomXMLPart</returns>
        public CustomXMLPart GetCustomXmlPartByGuid(int documentKey, Guid guid)
        {
            Document wordDocument = this.PackageHost.GetDocumentByDocumentKey(documentKey);
            string strGuid = "{" + guid.ToString().ToUpper() + "}";
            return wordDocument.CustomXMLParts.SelectByID(strGuid);
        }

        /// <summary>
        /// Remove all data and the catalog.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        public void Clear(int documentKey)
        {
            Document document = this.PackageHost.GetDocumentByDocumentKey(documentKey);
            CustomXMLPart catalog = GetCatalog(document);
            CustomXMLNode root = catalog.DocumentElement;
            CustomXMLNodes nodes = root.SelectNodes("//xmlpart");
            CustomXMLNode guidNode;
            CustomXMLPart part;

            // delete all data
            foreach (CustomXMLNode node in nodes)
            {
                guidNode = node.SelectSingleNode("@guid");
                string strGuid = guidNode.NodeValue;
                part = document.CustomXMLParts.SelectByID(strGuid);

                if (part != null)
                {
                    part.Delete();
                }
            }

            // delete catalog
            catalog.Delete();
        }

        /// <summary>
        /// Deletes a customxmlpart and removes it from the catalog
        /// </summary>
        /// <param name="id">The id.</param>
        public void DeleteCustomXmlPartById(string id)
        {
            CustomXMLNode customXMLNodeRoot = GetCatalog(this.wordApplication.ActiveDocument).DocumentElement;
            string storedGuid = GetCustomXMLNodeGuid(id, customXMLNodeRoot);
            this.DeleteCustomXmlPartByGuid(new Guid(storedGuid));

            RemoveChildNodes(customXMLNodeRoot, id, IdNode);
        }

        /// <summary>
        /// Deletes a customxmlpart and removes it from the catalog
        /// </summary>
        /// <param name="guid">The GUID.</param>
        public void DeleteCustomXmlPartByGuid(Guid guid)
        {
            string strGuid = "{" + guid.ToString().ToUpper(CultureInfo.InvariantCulture) + "}";
            CustomXMLPart deleteCustomXMLPart = this.wordApplication.ActiveDocument.CustomXMLParts.SelectByID(strGuid);

            if (deleteCustomXMLPart != null)
            {
                deleteCustomXMLPart.Delete();
            }

            // remove entry from catalog that references this guid
            CustomXMLNode customXMLNodeRoot = GetCatalog(this.wordApplication.ActiveDocument).DocumentElement;
            RemoveChildNodes(customXMLNodeRoot, strGuid, GuidNode);
        }

        /// <summary>
        /// Remove all data and the catalog.
        /// </summary>
        public void ClearCustomXmlNodes()
        {
            CustomXMLNode rootCustomXMLNode = GetCatalog(this.wordApplication.ActiveDocument).DocumentElement;
            CustomXMLNodes customXMLNodes = rootCustomXMLNode.SelectNodes("//xmlpart");
            CustomXMLNode guidCustomXMLNode;
            CustomXMLPart customXMLPart;

            // delete all data
            foreach (CustomXMLNode currentCustomXMLNode in customXMLNodes)
            {
                guidCustomXMLNode = currentCustomXMLNode.SelectSingleNode(GuidNode);
                string guidValue = guidCustomXMLNode.NodeValue;
                customXMLPart = this.wordApplication.ActiveDocument.CustomXMLParts.SelectByID(guidValue);

                if (customXMLPart != null)
                {
                    customXMLPart.Delete();
                }
            }

            // delete catalog
            GetCatalog(this.wordApplication.ActiveDocument).Delete();
        }

        /// <summary>
        /// Sets the next field.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="lsid">The lsid.</param>
        /// <param name="next">The next.</param>
        public void SetNextNodeCustomXmlPart(int documentKey, string lsid, string next)
        {
            Document document = this.PackageHost.GetDocumentByDocumentKey(documentKey);
            CustomXMLNode customXMLRootNode = GetCatalog(document).DocumentElement;
            CustomXMLNodes customXMLNodes = customXMLRootNode.SelectNodes("//xmlpart");
            CustomXMLNode attributeNode;

            foreach (CustomXMLNode node in customXMLNodes)
            {
                attributeNode = node.SelectSingleNode(IdNode);
                if (attributeNode.NodeValue.CompareIgnoreCase(lsid))
                {
                    string xmlPart = GetXMLForCustomXMLNode(node, next);
                    customXMLRootNode.RemoveChild(node);
                    customXMLRootNode.AppendChildSubtree(xmlPart);
                    return;
                }
            }
        }

        /// <summary>
        /// Gets the next GUID.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="currentGuid">The current GUID.</param>
        /// <returns>string</returns>
        public string GetNextNodeGuid(int documentKey, string currentGuid)
        {
            Document document = this.PackageHost.GetDocumentByDocumentKey(documentKey);
            CustomXMLNode customXMLNodeRoot = GetCatalog(document).DocumentElement;
            CustomXMLNodes customXMLNodes = customXMLNodeRoot.SelectNodes("//xmlpart");

            CustomXMLNode guidNode;
            CustomXMLNode nextNode;
            CustomXMLNode lsidNode;

            foreach (CustomXMLNode node in customXMLNodes)
            {
                guidNode = node.SelectSingleNode(GuidNode);

                // If Current is equal to Guid, then proceed further 
                if (guidNode.NodeValue.CompareIgnoreCase(currentGuid))
                {
                    // Get Next Node 
                    nextNode = node.SelectSingleNode("@next");

                    // If Next Node or it's value is null, then return
                    if (nextNode == null || nextNode.NodeValue == null)
                    {
                        return null;
                    }

                    foreach (CustomXMLNode idNode in customXMLNodes)
                    {
                        lsidNode = idNode.SelectSingleNode(IdNode);

                        if ((lsidNode != null) && lsidNode.NodeValue.CompareIgnoreCase(nextNode.NodeValue))
                        {
                            return idNode.SelectSingleNode(GuidNode).NodeValue;
                        }
                    }

                    break;
                }
            }

            return null;
        }

        /// <summary>
        /// Gets the next GUID.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="currentLsid">The current lsid.</param>
        /// <returns>string</returns>
        public string GetNextNodeLsid(int documentKey, string currentLsid)
        {
            Document document = this.PackageHost.GetDocumentByDocumentKey(documentKey);
            CustomXMLNode customXMLNodeRoot = GetCatalog(document).DocumentElement;
            CustomXMLNodes customXMLNodes = customXMLNodeRoot.SelectNodes("//xmlpart");

            CustomXMLNode nextNode;
            CustomXMLNode lsidNode;
            string nextLsid = string.Empty;

            foreach (CustomXMLNode customXMLNode in customXMLNodes)
            {
                lsidNode = customXMLNode.SelectSingleNode(IdNode);
                
                if (lsidNode.NodeValue.CompareIgnoreCase(currentLsid))
                {
                    nextNode = customXMLNode.SelectSingleNode("@next");
                    if (nextNode == null || nextNode.NodeValue == null)
                    {
                        nextLsid = string.Empty;
                    }
                    else
                    {
                        nextLsid = nextNode.NodeValue;
                    }

                    break;
                }
            }

            return nextLsid;
        }

        /// <summary>
        /// Creates the new part.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <param name="catalogRoot">The catalog root.</param>
        /// <param name="wordDocument">The word document.</param>
        /// <returns>CustomXMLPart</returns>
        private static CustomXMLPart CreateCustomXMLPart(string id, CustomXMLNode catalogRoot, Document wordDocument)
        {
            CustomXMLPart newCustomXMLPart = wordDocument.CustomXMLParts.Add("<root></root>", missingObject);
            catalogRoot.AppendChildSubtree("<xmlpart id=\"" + id.ToUpper() + "\" guid=\"" + newCustomXMLPart.Id + "\" />");
            return newCustomXMLPart;
        }

        /// <summary>
        /// Gets the stored GUID.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <param name="catalogRoot">The catalog root.</param>
        /// <returns>string</returns>
        private static string GetCustomXMLNodeGuid(string id, CustomXMLNode catalogRoot)
        {
            CustomXMLNodes customXMLNodes = catalogRoot.SelectNodes("//xmlpart");
            CustomXMLNode idCustomXMLNode;

            foreach (CustomXMLNode currentCustomXmlNode in customXMLNodes)
            {
                idCustomXMLNode = currentCustomXmlNode.SelectSingleNode(IdNode);

                if (idCustomXMLNode.NodeValue.CompareIgnoreCase(id))
                {
                    return currentCustomXmlNode.SelectSingleNode(GuidNode).NodeValue;
                }
            }

            return null;
        }

        /// <summary>
        /// Adds the catalog to custom XML parts.
        /// </summary>
        /// <param name="customXMLParts">The custom XML parts.</param>
        /// <returns>CustomXMLPart</returns>
        private static CustomXMLPart AddCatalogToCustomXMLParts(CustomXMLParts customXMLParts)
        {
            string catalogXml = "<catalog xmlns=\"" + CatalogNamespace + "\"></catalog>";
            return customXMLParts.Add(catalogXml, missingObject);
        }
     
        /// <summary>
        /// Gets the catalog.
        /// </summary>
        /// <param name="wordDocument">The word document.</param>
        /// <returns>CustomXMLPart</returns>
        private static CustomXMLPart GetCatalog(Document wordDocument)
        {
            CustomXMLParts customXmlParts = wordDocument.CustomXMLParts.SelectByNamespace(CatalogNamespace);
            if (customXmlParts.Count == 0)
            {
                // Check for document protection
                if (wordDocument.ProtectionType == WdProtectionType.wdNoProtection)
                {
                    return AddCatalogToCustomXMLParts(customXmlParts);
                }
            }

            foreach (CustomXMLPart currentCustomXmlPart in customXmlParts)
            {
                return currentCustomXmlPart;
            }

            return null;
        }

        /// <summary>
        /// Gets the XML for custom XML node.
        /// </summary>
        /// <param name="customXMLNode">The custom XML node.</param>
        /// <param name="next">The next.</param>
        /// <returns>string</returns>
        private static string GetXMLForCustomXMLNode(CustomXMLNode customXMLNode, string next)
        {
            string xml = "<xmlpart id=\"" + customXMLNode.SelectSingleNode(IdNode).NodeValue + "\" guid=\"" + customXMLNode.SelectSingleNode(GuidNode).NodeValue;

            if (next != null)
            {
                return xml + "\" next=\"" + next + "\" />";
            }

            return xml + "\" />";
        }

        #endregion
    }
}
